﻿#region Using directives

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;
using Microsoft.VisualBasic;

#endregion

namespace Weazel.Math
{
  public class AdditionDelegate
  {
    public delegate uint[] AdditionInvoker(uint[] result, uint[] a, uint[] b);

    public AdditionDelegate()
    {
    }

    private void EmitIndexPosition(ILGenerator il, int position)
    {
      switch (position)
      {
        case 0: il.Emit(OpCodes.Ldc_I4_0); break;
        case 1: il.Emit(OpCodes.Ldc_I4_1); break;
        case 2: il.Emit(OpCodes.Ldc_I4_2); break;
        case 3: il.Emit(OpCodes.Ldc_I4_3); break;
        case 4: il.Emit(OpCodes.Ldc_I4_4); break;
        case 5: il.Emit(OpCodes.Ldc_I4_5); break;
        case 6: il.Emit(OpCodes.Ldc_I4_6); break;
        case 7: il.Emit(OpCodes.Ldc_I4_7); break;
        case 8: il.Emit(OpCodes.Ldc_I4_8); break;

        default:
          il.Emit(OpCodes.Ldc_I4, position);
          break;
      }
    }

    private void EmitLoadParameter1EntryOnStack(ILGenerator il, int position)
    {
      il.Emit(OpCodes.Ldarg_1);       // load parameter 1      
      EmitIndexPosition(il, position);    // load position          
      il.Emit(OpCodes.Ldelem_U4);     // perform lookup -> result is left on the stack
      il.Emit(OpCodes.Conv_I8);       // convert to 64-bits signed           
    }

    private void EmitStoreTopOfStackToResultEntry(ILGenerator il, int position)
    {
      il.Emit(OpCodes.Ldarg_0);           // load result array            
      EmitIndexPosition(il, position);    // load position
      il.Emit(OpCodes.Ldloc_2);
      il.Emit(OpCodes.Stelem_I4);         // pop top of stack; store at position 'position' in parameter 1    
      il.Emit(OpCodes.Nop);
    }

    private void EmitLoadParameter2EntryOnStack(ILGenerator il, int position)
    {
      il.Emit(OpCodes.Ldarg_2);       // load parameter 2 
      EmitIndexPosition(il, position);    // load position
      il.Emit(OpCodes.Ldelem_U4);     // perform lookup -> result is left on the stack
      il.Emit(OpCodes.Conv_I8);       // convert to 64-bits signed

      
    }

    private void EmitAddBothOperands(ILGenerator il, int position)
    {
      EmitLoadParameter1EntryOnStack(il, position);
      EmitLoadParameter2EntryOnStack(il, position);

      il.Emit(OpCodes.Add);           // add a and b pushing the result on the stack

      il.Emit(OpCodes.Ldloc_1);       // load carry 
      il.Emit(OpCodes.Conv_I8);       // convert to ulong

      il.Emit(OpCodes.Add);           // add (a + b) and carry

      il.Emit(OpCodes.Dup);           // duplicate result
      il.Emit(OpCodes.Stloc_2);

      il.Emit(OpCodes.Conv_U4);       // convert to uint32

      EmitStoreTopOfStackToResultEntry(il, position);

      il.Emit(OpCodes.Ldc_I4_S, 32);  // push 32 onto the stack
      il.Emit(OpCodes.Shr);           // shift add result 32 bits right
      il.Emit(OpCodes.Conv_U4);       // convert to uint32
      il.Emit(OpCodes.Stloc_1);       // store as carry

    }

    private void EmitAddOperand1AndCarry(ILGenerator il, int position)
    {
      EmitLoadParameter1EntryOnStack(il, position);

      il.Emit(OpCodes.Ldloc_1);       // load carry 
      il.Emit(OpCodes.Conv_U8);       // convert to ulong

      il.Emit(OpCodes.Add);           // add (a + b) and carry
      il.Emit(OpCodes.Dup);           // duplicate result

      il.Emit(OpCodes.Conv_U4);       // convert to uint32
      EmitStoreTopOfStackToResultEntry(il, position);

      il.Emit(OpCodes.Ldc_I4_S, 32);  // push 32 onto the stack
      il.Emit(OpCodes.Shr);           // shift add result 32 bits right
      il.Emit(OpCodes.Conv_U4);       // convert to uint32
      il.Emit(OpCodes.Stloc_1);       // store as carry
    }

    private void EmitAddOperand2AndCarry(ILGenerator il, int position)
    {
      EmitLoadParameter2EntryOnStack(il, position);

      il.Emit(OpCodes.Ldloc_1);       // load carry 
      il.Emit(OpCodes.Conv_U8);       // convert to ulong

      il.Emit(OpCodes.Add);           // add (a + b) and carry
      il.Emit(OpCodes.Dup);           // duplicate result

      il.Emit(OpCodes.Conv_U4);       // convert to uint32
      EmitStoreTopOfStackToResultEntry(il, position);

      il.Emit(OpCodes.Ldc_I4_S, 32);  // push 32 onto the stack
      il.Emit(OpCodes.Shr);           // shift add result 32 bits right
      il.Emit(OpCodes.Conv_U4);       // convert to uint32
      il.Emit(OpCodes.Stloc_1);       // store as carry
    }

    private void EmitSetCarry(ILGenerator il, int position)
    {
      il.Emit(OpCodes.Ldloc_1);       // load carry      
      il.Emit(OpCodes.Conv_U4);       // convert to uint32
      EmitStoreTopOfStackToResultEntry(il, position);
    }

    private object[] o;
    public void init(uint[] r, uint[] a, uint[] b)
    {
      o = new object[] { r, a, b };
    }

    public uint[] Invoke()
    {
      return (uint[])addMethod.Invoke(null, o);
    }

    private DynamicMethod addMethod;

    public void Create(int resultWidth, bool resultIsSigned, int parameterOneWidth, bool parameterOneSigned, int parameterTwoWidth, bool parameterTwoSigned)
    {
      Type[] parameterTypes = new Type[] 
      {
        typeof(uint[]),
        typeof(uint[]),
        typeof(uint[])
      };


      addMethod = new DynamicMethod("add", MethodAttributes.Static | MethodAttributes.Public, CallingConventions.Standard, typeof(uint[]), parameterTypes, typeof(AdditionDelegate), true);

      ILGenerator il = addMethod.GetILGenerator();

      LocalBuilder lb1 = il.DeclareLocal(typeof(int));
      LocalBuilder lb2 = il.DeclareLocal(typeof(int));
      LocalBuilder lb3 = il.DeclareLocal(typeof(int));
      LocalBuilder lb4 = il.DeclareLocal(typeof(int));

      

      parameterOneWidth = (parameterOneWidth % 32) != 0 ? (parameterOneWidth / 32) + 1 : (parameterOneWidth / 32);
      parameterTwoWidth = (parameterTwoWidth % 32) != 0 ? (parameterTwoWidth / 32) + 1 : (parameterTwoWidth / 32);
      resultWidth = (resultWidth % 32) != 0 ? (resultWidth / 32) + 1 : (resultWidth / 32);

      il.Emit(OpCodes.Ldc_I4_0);
      il.Emit(OpCodes.Stloc_0);

      il.Emit(OpCodes.Ldc_I4_0);
      il.Emit(OpCodes.Stloc_1);
      
      il.Emit(OpCodes.Ldc_I4_0);
      il.Emit(OpCodes.Stloc_2);
      

      if (parameterOneWidth <= parameterTwoWidth)
      {
        if (resultWidth <= parameterOneWidth)          // result < param1 < param2
        {
          for (int i = 0; i < resultWidth; i++)
            EmitAddBothOperands(il, i);
        }
        else if (parameterTwoWidth < resultWidth)     // param1 < param2 < result
        {
          int i = 0;
          for (; i < parameterOneWidth; i++)
            EmitAddBothOperands(il, i);
          for (; i < parameterTwoWidth; i++)
            EmitAddOperand2AndCarry(il, i);

          EmitSetCarry(il, i);
        }
        else                                          // param1 < result < param2
        {
          int i = 0;
          for (; i < parameterOneWidth; i++)
            EmitAddBothOperands(il, i);
          for (; i < resultWidth; i++)
            EmitAddOperand2AndCarry(il, i);
        }
      }
      else // paramterTwoWidth < paramterOneWidth
      {
        if (resultWidth <= parameterTwoWidth)          // result < param2 < param1
        {
          for (int i = 0; i < resultWidth; i++)
            EmitAddBothOperands(il, i);
        }
        else if (resultWidth > parameterOneWidth)     // param2 < param1 < result
        {
          int i = 0;
          for (; i < parameterTwoWidth; i++)
            EmitAddBothOperands(il, i);
          for (; i < parameterOneWidth; i++)
            EmitAddOperand2AndCarry(il, i);

          EmitSetCarry(il, i);
        }
        else                                          // param2 < result < param1
        {
          int i = 0;
          for (; i < parameterTwoWidth; i++)
            EmitAddBothOperands(il, i);
          for (; i < resultWidth; i++)
            EmitAddOperand2AndCarry(il, i);
        }
      }

      il.Emit(OpCodes.Ldarg_0);
      il.Emit(OpCodes.Ret);




     // return null;
      //return (AdditionInvoker)addMethod.CreateDelegate(typeof(AdditionInvoker));
    }


  }
}
